namespace System.Workflow.ComponentModel
{
    using System;
    using System.Diagnostics;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;

    [Obsolete("The System.Workflow.* types are deprecated.  Instead, please use the new types from System.Activities.*")]
    public interface IActivityEventListener<T> where T : EventArgs
    {
        void OnEvent(object sender, T e);
    }

    [Serializable]
    internal sealed class ActivityExecutorDelegateInfo<T> where T : EventArgs
    {
        private string activityQualifiedName = null;
        private IActivityEventListener<T> eventListener = null;
        private EventHandler<T> delegateValue = null;
        private int contextId = -1;
        private bool wantInTransact = false;
        private string subscribedActivityQualifiedName = null;

        public ActivityExecutorDelegateInfo(EventHandler<T> delegateValue, Activity contextActivity)
            : this(false, delegateValue, contextActivity)
        {
        }
        public ActivityExecutorDelegateInfo(IActivityEventListener<T> eventListener, Activity contextActivity)
            : this(false, eventListener, contextActivity)
        {
        }
        public ActivityExecutorDelegateInfo(EventHandler<T> delegateValue, Activity contextActivity, bool wantInTransact)
            : this(delegateValue, contextActivity)
        {
            this.wantInTransact = wantInTransact;
        }
        public ActivityExecutorDelegateInfo(IActivityEventListener<T> eventListener, Activity contextActivity, bool wantInTransact)
            : this(eventListener, contextActivity)
        {
            this.wantInTransact = wantInTransact;
        }
        internal ActivityExecutorDelegateInfo(bool useCurrentContext, EventHandler<T> delegateValue, Activity contextActivity)
        {
            this.delegateValue = delegateValue;
            Activity target = delegateValue.Target as Activity;

            if (contextActivity.WorkflowCoreRuntime != null)
            {
                if (useCurrentContext)
                    this.contextId = contextActivity.WorkflowCoreRuntime.CurrentActivity.ContextActivity.ContextId;
                else
                    this.contextId = contextActivity.ContextId;

                this.activityQualifiedName = (target ?? contextActivity.WorkflowCoreRuntime.CurrentActivity).QualifiedName;
            }
            else
            {
                this.contextId = 1;
                this.activityQualifiedName = (target ?? contextActivity.RootActivity).QualifiedName;
            }
        }
        internal ActivityExecutorDelegateInfo(bool useCurrentContext, IActivityEventListener<T> eventListener, Activity contextActivity)
        {
            this.eventListener = eventListener;
            Activity target = eventListener as Activity;
            if (contextActivity.WorkflowCoreRuntime != null)
            {
                if (useCurrentContext)
                    this.contextId = contextActivity.WorkflowCoreRuntime.CurrentActivity.ContextActivity.ContextId;
                else
                    this.contextId = contextActivity.ContextId;

                this.activityQualifiedName = (target ?? contextActivity.WorkflowCoreRuntime.CurrentActivity).QualifiedName;
            }
            else
            {
                this.contextId = 1;
                this.activityQualifiedName = (target ?? contextActivity.RootActivity).QualifiedName;
            }
        }
        public string ActivityQualifiedName
        {
            get
            {
                return this.activityQualifiedName;
            }
        }
        public string SubscribedActivityQualifiedName
        {
            get
            {
                return this.subscribedActivityQualifiedName;
            }
            set
            {
                this.subscribedActivityQualifiedName = value;
            }

        }
        public int ContextId
        {
            get
            {
                return this.contextId;
            }
        }
        public EventHandler<T> HandlerDelegate
        {
            get
            {
                return this.delegateValue;
            }
        }

        public IActivityEventListener<T> EventListener
        {
            get
            {
                return this.eventListener;
            }
        }

        internal void InvokeDelegate(Activity currentContextActivity, T e, bool sync, bool transacted)
        {
            Activity targetContextActivity = currentContextActivity.WorkflowCoreRuntime.GetContextActivityForId(this.contextId);
            if (targetContextActivity == null)
            {
                targetContextActivity = FindExecutorForActivityUp(currentContextActivity, this.activityQualifiedName);
                if (targetContextActivity == null)
                    targetContextActivity = FindExecutorForActivityDown(currentContextActivity, this.activityQualifiedName);
            }
            if (targetContextActivity != null)
                InvokeDelegate(currentContextActivity, targetContextActivity, e, sync, transacted);
        }
        public void InvokeDelegate(Activity currentContextActivity, T e, bool transacted)
        {
            // If in atomic and subscriber in same scope, or not in atomic scope at all
            Activity targetContextActivity = FindExecutorForActivityUp(currentContextActivity, this.activityQualifiedName);
            if (targetContextActivity == null)
                targetContextActivity = FindExecutorForActivityDown(currentContextActivity, this.activityQualifiedName);

            if (targetContextActivity != null)
                InvokeDelegate(currentContextActivity, targetContextActivity, e, false, transacted);
        }
        private void InvokeDelegate(Activity currentContextActivity, Activity targetContextActivity, T e, bool sync, bool transacted)
        {
            ActivityExecutorDelegateOperation delegateOperation = null;
            if (this.delegateValue != null)
                delegateOperation = new ActivityExecutorDelegateOperation(this.activityQualifiedName, this.delegateValue, e, this.ContextId);
            else
                delegateOperation = new ActivityExecutorDelegateOperation(this.activityQualifiedName, this.eventListener, e, this.ContextId);

            bool mayInvokeDelegateNow = MayInvokeDelegateNow(currentContextActivity);
            if (mayInvokeDelegateNow && sync)
            {
                Activity targetActivity = targetContextActivity.GetActivityByName(this.activityQualifiedName);
                using (currentContextActivity.WorkflowCoreRuntime.SetCurrentActivity(targetActivity))
                {
                    delegateOperation.SynchronousInvoke = true;
                    delegateOperation.Run(currentContextActivity.WorkflowCoreRuntime);
                }
            }
            else
            {
                // If in atomic and subscriber not in same scope
                // Queue it on the subscriber's baseExecutor
                Activity targetActivity = targetContextActivity.GetActivityByName(this.activityQualifiedName);
                currentContextActivity.WorkflowCoreRuntime.ScheduleItem(delegateOperation, ActivityExecutionContext.IsInAtomicTransaction(targetActivity), transacted, !mayInvokeDelegateNow);
            }
        }
        private bool MayInvokeDelegateNow(Activity currentContextActivity)
        {
            // Ok to invoke right away if 
            // subscriber wants to participate in the current transaction
            if ((this.activityQualifiedName == null) || (this.wantInTransact))
                return true;

            // If not in atomic scope at all,            
            if (!ActivityExecutionContext.IsInAtomicTransaction(currentContextActivity.WorkflowCoreRuntime.CurrentActivity))
                return true;

            // Has not started executing yet, queue it up for now
            // Not letting it leak out for recv case any more
            Activity targetContextActivity = currentContextActivity.WorkflowCoreRuntime.GetContextActivityForId(this.contextId);
            if (targetContextActivity == null)
                return false;

            // or in atomic and subscriber in same scope,
            // or in an atomic scope that's not in executing state, e.g. need to fire Scope closed status
            Activity targetActivity = targetContextActivity.GetActivityByName(this.activityQualifiedName, true);

            if (targetActivity == null)
                return false;

            if (ActivityExecutionContext.IsInAtomicTransaction(targetActivity) &&
                ActivityExecutionContext.IsInAtomicTransaction(currentContextActivity.WorkflowCoreRuntime.CurrentActivity))
                return true;

            // If the activity receiving the subscription is the scope itself
            if (targetActivity.MetaEquals(currentContextActivity))
                return true;

            return false;
        }
        private Activity FindExecutorForActivityUp(Activity contextActivity, string activityQualifiedName)
        {
            while (contextActivity != null)
            {
                Activity activityToFind = contextActivity.GetActivityByName(activityQualifiedName, true);
                if (activityToFind != null && activityToFind.ExecutionStatus != ActivityExecutionStatus.Initialized)
                    return contextActivity;
                contextActivity = contextActivity.ParentContextActivity;
            }
            return contextActivity;
        }
        private Activity FindExecutorForActivityDown(Activity contextActivity, string activityQualifiedName)
        {
            Queue<Activity> contextActivities = new Queue<Activity>();
            contextActivities.Enqueue(contextActivity);
            while (contextActivities.Count > 0)
            {
                Activity contextActivity2 = contextActivities.Dequeue();
                Activity activityToFind = contextActivity2.GetActivityByName(activityQualifiedName, true);
                if (activityToFind != null && activityToFind.ExecutionStatus != ActivityExecutionStatus.Initialized)
                    return contextActivity2;

                IList<Activity> nestedContextActivities = (IList<Activity>)contextActivity2.GetValue(Activity.ActiveExecutionContextsProperty);
                if (nestedContextActivities != null)
                {
                    foreach (Activity nestedContextActivity in nestedContextActivities)
                        contextActivities.Enqueue(nestedContextActivity);
                }
            }
            return null;
        }
        public override bool Equals(object obj)
        {
            ActivityExecutorDelegateInfo<T> otherObject = obj as ActivityExecutorDelegateInfo<T>;
            if (otherObject == null)
                return false;

            return (
                            (otherObject.delegateValue == null && this.delegateValue == null) ||
                            (otherObject.delegateValue != null && otherObject.delegateValue.Equals(this.delegateValue))
                        ) &&
                        (
                            (otherObject.eventListener == null && this.eventListener == null) ||
                            (otherObject.eventListener != null && otherObject.eventListener.Equals(this.eventListener))
                        ) &&
                        otherObject.activityQualifiedName == this.activityQualifiedName &&
                        otherObject.contextId == this.contextId &&
                        otherObject.wantInTransact == this.wantInTransact;
        }
        public override int GetHashCode()
        {
            return this.delegateValue != null ? this.delegateValue.GetHashCode() : this.eventListener.GetHashCode() ^
                    this.activityQualifiedName.GetHashCode();
        }

        [Serializable]
        private sealed class ActivityExecutorDelegateOperation : SchedulableItem
        {
            private string activityQualifiedName = null;
            private IActivityEventListener<T> eventListener = null;
            private EventHandler<T> delegateValue = null;
            private T args = null;

            [NonSerialized]
            private bool synchronousInvoke = false;

            public ActivityExecutorDelegateOperation(string activityQualifiedName, EventHandler<T> delegateValue, T e, int contextId)
                : base(contextId, activityQualifiedName)
            {
                this.activityQualifiedName = activityQualifiedName;
                this.delegateValue = delegateValue;
                this.args = e;
            }
            public ActivityExecutorDelegateOperation(string activityQualifiedName, IActivityEventListener<T> eventListener, T e, int contextId)
                : base(contextId, activityQualifiedName)
            {
                this.activityQualifiedName = activityQualifiedName;
                this.eventListener = eventListener;
                this.args = e;
            }
            internal bool SynchronousInvoke
            {
                get
                {
                    return this.synchronousInvoke;
                }
                set
                {
                    this.synchronousInvoke = value;
                }
            }
            public override bool Run(IWorkflowCoreRuntime workflowCoreRuntime)
            {
                // get context activity
                Activity contextActivity = workflowCoreRuntime.GetContextActivityForId(this.ContextId);

                // Work around for ActivityExecutionStatusChangedEventArgs
                ActivityExecutionStatusChangedEventArgs activityStatusChangeEventArgs = this.args as ActivityExecutionStatusChangedEventArgs;
                if (activityStatusChangeEventArgs != null)
                {
                    activityStatusChangeEventArgs.BaseExecutor = workflowCoreRuntime;
                    if (activityStatusChangeEventArgs.Activity == null)
                    {
                        // status change for an activity that has been deleted dynamically since.
                        activityStatusChangeEventArgs.BaseExecutor = null;
                        return false;
                    }
                }

                // get activity, if null, or if activity has already closed or just initialized, or if primary has closed and 
                // the target of the notification is not ActivityExecutionFilter, then 
                Activity activity = contextActivity.GetActivityByName(this.activityQualifiedName);
                if (activity == null ||
                      ((activity.ExecutionStatus == ActivityExecutionStatus.Closed || activity.ExecutionStatus == ActivityExecutionStatus.Initialized) && !this.synchronousInvoke) ||
                      (activity.HasPrimaryClosed && !(this.eventListener is ActivityExecutionFilter))
                    )
                    return false;

                // call the delegate
                try
                {
                    using (workflowCoreRuntime.SetCurrentActivity(activity))
                    {
                        using (ActivityExecutionContext activityExecutionContext = new ActivityExecutionContext(activity))
                        {
                            if (this.delegateValue != null)
                                this.delegateValue(activityExecutionContext, this.args);
                            else
                                this.eventListener.OnEvent(activityExecutionContext, this.args);
                        }
                    }
                }
                catch (Exception e)
                {
                    if (activity != null)
                        System.Workflow.Runtime.WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 1, "Subscription handler of Activity {0} threw {1}", activity.QualifiedName, e.ToString());
                    else
                        System.Workflow.Runtime.WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 1, "Subscription handler threw {0}", e.ToString());
                    throw;
                }
                finally
                {
                    // Work around for activity status change Event Args
                    if (activityStatusChangeEventArgs != null)
                        activityStatusChangeEventArgs.BaseExecutor = null;
                }
                return true;
            }
            public override string ToString()
            {
                return "SubscriptionEvent(" + "(" + this.ContextId.ToString(CultureInfo.CurrentCulture) + ")" + this.activityQualifiedName + ", " + this.args.ToString() + ")";
            }
        }
    }
}
